#!/usr/bin/python
from __future__ import with_statement
import glob
import filecmp
import os
import re
import sys
import shutil
import subprocess
import thread

import ts_os

OPTIONS_TO_SHOW = [
	'stl',
	'rtti',
	'exceptions',
	'debug',
	'thread'
]

def get_overlapping_objects(project1, project2):
	base_path1 = project1.get_dir()
	base_path2 = project2.get_dir()

	source_list1 = []
	for s in project1.get_config().get_variable_value('SOURCES').split():
		s = os.path.join(base_path1, s)
		source_list1.append(os.path.normpath(s))

	source_list2 = []
	for s in project2.get_config().get_variable_value('SOURCES').split():
		s = os.path.join(base_path2, s)
		source_list2.append(os.path.normpath(s))

	module_list = []
	overlapping_module_list = []

	for s in (ts_os.get_file_list_operation(source_list1, source_list2, 'd') \
		+ ts_os.get_file_list_operation(source_list2, source_list1, 'd')):	
		(s, tmp) = os.path.splitext(os.path.basename(s))
		if s in module_list:
			overlapping_module_list.append(s)
		else:
			module_list.append(s)
	return overlapping_module_list

class operand(object):
	def __init__(self, value, variables = {}):
		self.__value = value
		self.__variables = variables
		return

	def evaluate(self):
		e = re.compile('(?P<dollar>[\$]{1,2})(?P<lparen>[\{\(]?)(?P<name>\w+)(?P<rparen>[\}\)]?)')
		s =  e.sub(self.__match, self.__value)
		s = s.strip()
		return s

	def __match(self, m):
		s = m.group(0)
		if (m.group('lparen') == '(') and (m.group('rparen') == ')'):
			try:
				s = os.environ[m.group('name').upper()]
			except:
				s = ''
		if ((m.group('lparen') == '{') and (m.group('rparen') == '}')) \
			or (not m.group('lparen') and not m.group('rparen')):
			try:
				s = self.__variables[m.group('name')]
			except:
				s = ''
		return s

def string_operation(operand1, operand2, op):
	r = operand1
	if op == '=':
		r = operand2
	elif op == '+=':
		r += (' ' + operand2)
	elif op == '-=':
		l = r.split()
		while operand2 in l:
			l.remove(operand2)
		r = ' '.join(l)
	elif op == '*=':
		l = r.split()
		if not (operand2 in l):
			r += (' ' + operand2)
	elif op == '~=':
		l = operand2.split('/')	
		if len(l) > 1:
			e = re.compile(l[1])
			rep = ''
			if len(l) > 2:
				rep = l[2]
			r = e.sub(rep, r)
	r = r.strip()
	return r

class config(object):
	def __init__( self ):
		self.__variables = {}

		try:
			self.__spec = os.environ['QMAKESPEC']
			self.__qtdir = os.environ['QTDIR']
		except:
			assert False, 'Error determining Qt3 settings: no QTMAKESPEC' \
				' or QTDIR defined'

		self.__init_hardcoded()
		self.__init_variables()

	def eval_func(self, func, param_list, variables, inv = False):
		r = False
		if func in ('win32', 'unix'):
			if func == 'win32':
				r = (os.name == 'nt')
			else:
				r = (os.name == 'posix')
		elif func == self.__spec:
			r = True
		elif func == 'contains':
			try:
				r = (param_list[1] in variables[param_list[0]])
			except:
				r = False
		elif func == 'count':
			try:
				r = (variables[param_list[0]].split() == int(param_list[1]))
			except:
				r = false
		elif func == 'exists':
			r = os.path.exists(param_list[0])
		elif func == 'equals':
			try:
				r = (variables[param_list[0]] == param_list[1])
			except:
				r = False
		elif func == 'isEmpty':
			r = not (variables[params[0]])
		elif func == 'system':
			r = (os.system(param_list[0]) == 0)
		else:
			try:
				r = (func in variables['CONFIG'])
			except:
				r = False
		if inv:
			r = not r
		return r

	def parse_file(self, file_name, variables = {}):
		e = re.compile('^(?P<name>\w+)\s*(?P<sign>[\=\+\-\*\~]+)\s*(?P<value>[^\s]+(\s+[^\s]+)*)$')
		fe = re.compile('^(?P<inv>\!)?\s*(?P<func>[^\s]+)\s*(\((?P<params>.+)\))?\s*\{$')
		ff = re.compile('^(?P<name>\w+)\s*\((?P<params>.+)\)$')
		fs = []
		try:
			with open( file_name, 'r' ) as f:
				line = ''
				for s in f:
					s = s.strip()
					l = len(s)
					if not l:
						continue

					if s[l - 1] == '\\':
						line += (' ' + s[:(l - 1)])
						continue

					if line:
						s = (line + ' ' + s).strip()
						line = ''

					#Conditions
					m = fe.match(s)
					if m:
						p = []
						if m.group('params'):
							p = operand(m.group('params'), variables).evaluate().split(',')
						fs.append( self.eval_func(m.group('func'), \
							p, variables, m.group('inv')))
						continue

					if s == '}':
						fs.pop()
						continue

					if False in fs:
						continue
					
					#Processing instructions
					m = ff.match(s)
					if m:
						if m.group('name') == 'include':
							include_file_name = \
								operand(m.group('params'), variables).evaluate()
							if os.path.isfile(include_file_name):
								variables = self.parse_file(include_file_name, \
									variables)	

					#Variables
					m = e.match(s)
					if m:
						v1 = ''
						k = m.group('name')
						if k in variables:
							v1 = variables[k]
						v2 = operand(m.group('value'), variables).evaluate()
						variables[k] = string_operation(v1, \
							v2, m.group('sign'))
		except IOError, er:
			assert False, 'Error parsing Qt3 variables:' \
				' %s' % str(er)
				#' file %s not found' % file_name
		return variables

	def __init_hardcoded(self):
		if self.__spec == 'win32-msvc':
			self.__debugger = 'msdev'
			self.__make = 'nmake'
			self.__binary_ext = '.exe'
			self.__vcproj_ext = '.dsp'
			self.__obj_ext = '.obj'
		elif self.__spec == 'win32-msvc.net':
			self.__debugger = 'vcexpress'
			self.__make = 'nmake'
			self.__binary_ext = '.exe'
			self.__vcproj_ext = '.vcproj'
			self.__obj_ext = '.obj'
		elif self.__spec == 'win32-g++':
			self.__debugger = 'gdb'
			self.__make = 'mingw32-make'
			self.__binary_ext = '.exe'
			self.__vcproj_ext = ''
			self.__obj_ext = '.o'
		elif self.__spec == 'linux-g++':
			self.__debugger = 'gdb'
			self.__make = 'make'
			self.__binary_ext = ''
			self.__vcproj_ext = ''
			self.__obj_ext = '.o'
		return

	def __init_variables(self):
		self.__variables = self.parse_file(os.path.join( self.__qtdir, \
			'mkspecs', self.__spec, 'qmake.conf'), self.__variables)
		#if os.name == 'nt':
		#	self.__variables = self.parse_file(os.path.join(self.__qtdir, \
		#	'.qtwinconfig'), self.__variables)
		return

	def __str__( self ):
		l = []	
		for s in self.get_variable_value('CONFIG').split():
			if s in OPTIONS_TO_SHOW: l.append(s)
		return	'%s: %s' % (self.get_spec(), ' '.join(l))

	def get_make( self ):
		return self.__make

	def get_spec( self ):
		return self.__spec

	def get_qtdir(self):
		return self.__qtdir

	def get_make_with_file( self, file ):
		pair = (self.__make, file )
		s = '%s --file=%s' % pair
		if self.__make == 'nmake':
			s = '%s /f %s' % pair
		return s

	def get_build( self ):
		if 'debug' in self.get_variable_value('CONFIG'):
			return 'debug'
		return 'release'

	def is_debug( self ):
		return self.get_build() == 'debug'

	def get_debugger( self ):
		return self.__debugger

	def set_variables( self, variables ):
		self.__variables = variables
		return

	def get_variables( self ):
		return self.__variables

	def get_variable_value( self, variable ):
		r = ''
		if variable in self.__variables:
			r = self.__variables[variable]
		return r

	def get_binary_ext( self ):
		return self.__binary_ext

	def get_vcproj_ext( self ):
		return self.__vcproj_ext

	def get_obj_ext( self ):
		return self.__obj_ext

	def get_config(self):
		l = []
		for s in self.get_variable_value('CONFIG').split():
			if not (s in l):
				l.append(s)
		return ' '.join(l)

class project( ts_os.chdir_to_file ):
	def __init__( self, project_file ):
		super( project, self ).__init__( project_file )

		self.__config = None
		self.__target = ''

		self.__pre_run_function = None
		self.__pre_build_function = None
		self.__execute_function = os.system
		self.__execute_and_detach_function = os.system

		self.update_config()
	
	def set_pre_run_function( self, value ):
		self.__pre_run_function = value

	def set_pre_build_function( self, value ):
		self.__pre_build_function = value

	def set_execute_function( self, value ):
		self.__execute_function = value

	def set_execute_and_detach_function( self, value ):
		self.__execute_and_detach_function = value

	def update_config( self ):
		self.__is_make_file_created = False
		self.__is_successfully_built = False

		self.__config = config()
		self.__config.set_variables( self.__config.parse_file( \
			os.path.join(self.get_dir(), self.get_file()), \
			self.__config.get_variables()) )

		try:
			self.__target = self.get_config().get_variable_value('TARGET')
		except:
			self.__target, s = os.path.splitext( self.get_file() )

		#if self.__config.get_binary_ext():
		#	self.__target += self.__config.get_binary_ext()

		self.__make_file = self.__target + '.Makefile'

		if os.name == 'nt':
			self.__vcproj = self.__target.replace('-', '_') \
				+ self.__config.get_vcproj_ext()
		return

	def get_target(self):
		return self.__target

	def get_vcproj(self):
		return self.__vcproj

	def get_target_with_binary_ext(self):
		return self.__target + self.__config.get_binary_ext()

	def get_config(self):
		return self.__config

	def create_make_file( self ):
		self.__is_make_file_created = \
			(self.__execute_function( 'qmake -o %s %s' \
			% ( self.__make_file, self.get_file())) == 0 )
		return self.__is_make_file_created

	def create_vcproject( self ):
		assert os.name == 'nt', 'Cannot create vcproject for this platform' 
		return (self.__execute_function('qmake -tp vc %s' % self.get_file()) \
			== 0)

	def build(self):
		if self.__pre_build_function:
			self.__pre_build_function(self)
		assert self.__is_make_file_created or self.create_make_file(), \
			'Error building project %s: cannot create makefile' \
			% self.get_file()
		self.__is_successfully_built = (self.__execute_function( '%s' \
			% self.__config.get_make_with_file(self.__make_file)) == 0)
		return self.__is_successfully_built

	def clean( self ):
		assert self.__is_make_file_created or self.create_make_file(), \
			'Error cleaning project %s: cannot create makefile' \
			% self.get_file()
		self.__is_successfully_built = False
		return (self.__execute_function( '%s %s' \
			% ( self.__config.get_make_with_file(self.__make_file), 'clean')) \
			== 0)

	def run( self ):
		if self.__pre_run_function:
			self.__pre_run_function(self)
		return (self.__execute_and_detach_function(os.path.join(os.curdir, \
			'%s' % self.__target)) == 0)

	def debug( self ):
		if self.__pre_run_function:
			self.__pre_run_function(self)

		s = self.__target
		if os.name == 'nt':
			s = self.__vcproj
		return (self.__execute_and_detach_function('%s %s' \
			% (self.__config.get_debugger(), s) ) == 0)


if __name__ == '__main__':
	#import pdb
	#pdb.set_trace()
	total = 0
	failed = 0
	for s in sys.argv[1:]:
		with project( s ) as q:
			print 'Processing %s ...' % s
			q.set_execute_function(ts_os.print_string_as_execute)
			if not q.build():
				failed += 1
			total += 1
	if failed:
		print 'Building %d projects out of %d failed' % (failed, total )

